import torch
import numpy as np
""" 简介：
    在整个Pytorch框架中, 所有的神经网络本质上都是一个autograd package(自动求导工具包)，
    autograd package提供了一个对Tensors上所有的操作进行自动微分的功能.
"""
"""torch.Tensor:
    1. torch.Tensor是整个package中的核心类, 如果将属性.requires_grad设置为True, 它将追踪在这个类上定义的所有操作. 
	   当代码要进行反向传播的时候, 直接调用.backward()就可以自动计算所有的梯度. 在这个Tensor上的所有梯度将被累加进属性.grad中.
	   
    2. 如果想终止一个Tensor在计算图中的追踪回溯, 只需要执行.detach()就可以将该Tensor从计算图中撤下, 在未来的回溯计算中也不会再计算该Tensor.
	
    3. 除了.detach(), 如果想终止对计算图的回溯, 也就是不再进行方向传播求导数的过程, 也可以采用代码块的方式with torch.no_grad():, 
	   这种方式非常适用于对模型进行预测的时候, 因为预测阶段不再需要对梯度进行计算.
"""
"""torch.Function:
   1. Function类是和Tensor类同等重要的一个核心类, 它和Tensor共同构建了一个完整的类, 
      每一个Tensor拥有一个.grad_fn属性, 代表引用了哪个具体的Function创建了该Tensor.
	  
   2. 如果某个张量Tensor是用户自定义的, 则其对应的grad_fn is None.
"""

"""1. 梯度设置"""
x1 = torch.ones(3, 3)
# print(x1)
x = torch.ones(2, 2, requires_grad=True)  # 刚定义的量的梯度是None的，x后续运算产生的量会带有梯度对象，反向传播时x才会有梯度
print(x)
y = x + 2
print(y)
# 打印Tensor的grad_fn属性:
print(x.grad_fn) # None
print(y.grad_fn) # 一个对象

print("---------------------------")
print("---------------------------")
z = y * y * 3
out = z.mean()
print(z)
print(out)

print("---------------------------")
print("---------------------------")
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)  # 默认false
a.requires_grad_(True)  # 注意修改属性时采用的内置方法时，别忘了有下划线
print(a.requires_grad)
b = (a * a).sum()
print(b)
print(b.grad_fn)
print("---------------------------")
print("---------------------------")

"""2. 反向传播  （反向传播是深度学习中最最重要的部分）"""
print(x.grad)
out.backward()   #以out为节点设置并从此处开始进行反向传播
print(x.grad)    #反向传播前为none，传播后才有数值

print(x.requires_grad)
print((x ** 2).requires_grad)
"""2.1 通过with torch.no_grad():关闭梯度，不进行求导."""
with torch.no_grad():             # 不对这段代码进行梯度运算，可用于训练好的模型进行检测，以及做顶层设计训练新增加的层而不改变原模型的参数
    print((x ** 2).requires_grad) # 这段代码梯度被关闭，数值不会被更新、改变
# print(x.requires_grad)

"""2.2 通过.detach()获得一个新的Tensor, 拥有相同的内容但不需要自动求导."""
print("---------------------------")
print(x.requires_grad)
y = x.detach()          # 复制x，但关闭梯度更新，即从计算图上撤下y
print(x.requires_grad)
print(y.requires_grad)
print(x.eq(y).all())    # .eq()只对值进行比较，与属性是否有梯度无关






